package com.ctshk.rpc.payment.utils.alipay;

import com.ctshk.rpc.payment.dto.AliPayConfig;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;

/**
 * @BelongsProject: ctshk-cloud
 * @BelongsPackage: com.ctshk.rpc.payment.utils.alipay
 * @Author: hef
 * @CreateTime: 2021-04-20 20:29
 * @Description: 描述
 */
public class AliPayCallbackVerify {

    /**
     * 支付宝异步消息验证地址
     * The verification URL of Alipay notification.
     */
    //沙箱网关异步消息验证地址
    //The verification URL of Alipay notification,sandbox environment.
//    private static final String HTTPS_VERIFY_URL = "https://openapi.alipaydev.com/gateway.do?service=notify_verify&";
    //线上网关异步消息验证地址，如商户使用的生产环境，请换成下面的生产环境的地址
    //The verification URL of Alipay notification,production environment.(pls use the below line instead if you were in production environment)

    private AliPayConfig config;

    public static boolean verify(Map<String, String> params, AliPayConfig config) {
        AliPayCallbackVerify aliPayCallbackVerify = new AliPayCallbackVerify(config);
        return aliPayCallbackVerify.verify(params);
    }

    public AliPayCallbackVerify(AliPayConfig config) {
        this.config = config;
    }

    /**
     * 验证消息是否是支付宝发出的合法消息
     * Verify whether it's a legal notification sent from Alipay
     *
     * @param params 通知返回来的参数数组The params sent back from notification
     * @return 验证结果The result of verification
     */
    private boolean verify(Map<String, String> params) {
        //判断responsetTxt是否为true，isSign是否为true
        //responsetTxt的结果不是true，与服务器设置问题、合作身份者ID、notify_id一分钟失效有关
        //isSign不是true，与安全校验码、请求时的参数格式（如：带自定义参数等）、编码格式有关
        //judgue whether responsetTxt and isSign is true
        //if responsetTxt is not true,the cause might be related to sever setting,merchant account and expiration time of notify_id(one minute).
        //if isSign is not true，the cause might be related to sign,charset and format of request str(eg:request with custom parameter etc.)
        String responseTxt = "false";
        if (params.get("notify_id") != null) {
            String notify_id = params.get("notify_id");
            responseTxt = verifyResponse(notify_id);
        }
        String sign = "";
        if (params.get("sign") != null) {sign = params.get("sign");}
        boolean isSign = getSignVeryfy(params, sign);
        //写日志记录（若要调试，请取消下面两行注释）
        //write the log(pls uncomment the below two lines if you would like to debug)
        //String sWord = "responseTxt=" + responseTxt + "\n isSign=" + isSign + "\n 返回回来的参数：" + AlipayCore.createLinkString(params);
        //AlipayCore.logResult(sWord);

        if (isSign && responseTxt.equals("true")) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 根据反馈回来的信息，生成签名结果
     * Generate sign from feedback
     *
     * @param Params 通知返回来的参数数组the params from the feedback notification
     * @param sign   比对的签名结果the sign to be compared
     * @return 生成的签名结果the result of verification
     */
    private boolean getSignVeryfy(Map<String, String> Params, String sign) {
        //过滤空值、sign与sign_type参数
        //Filter parameters with null value ,sign and sign_type
        Map<String, String> sParaNew = paraFilter(Params);
        //获取待签名字符串
        //Generate the pre-sign string
        String preSignStr = createLinkString(sParaNew);
        //获得签名验证结果
        //get the result of verification
//        boolean isSign = false;
//        if(this.config.getSignType().equals("MD5") ) {
//        isSign = MD5.verify(preSignStr, sign, this.config.getKey(), "UTF-8");
//        String checkSign = AliPayUtil.RSA2(preSignStr, this.config.getRsa2Key(), "UTF-8");
        boolean b = AliPayUtil.rsaVerify(preSignStr, sign, this.config.getAliRsaPublicKey(), "UTF-8");
//        }
        return b;
    }

    /**
     * 获取远程服务器ATN结果,验证返回URL
     * Get the remote server ATN result,return URL
     *
     * @param notify_id 通知校验IDThe ID for a particular notification.
     * @return 服务器ATN结果Sever ATN result
     * 验证结果集：
     * Verification result:
     * invalid命令参数不对 出现这个错误，请检测返回处理中partner和key是否为空
     * invalid Pls check whether the partner and key are null from notification
     * true 返回正确信息
     * true return the right info
     * false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
     * false pls check the firewall or the server block certain port,also pls note the expiration time is 1 minute
     */
    private String verifyResponse(String notify_id) {
        //获取远程服务器ATN结果，验证是否是支付宝服务器发来的请求
        //Get the remote server ATN result,verify whether it's from Alipay
        String veryfyUrl = this.config.getReqUrl() + "?service=notify_verify&partner=" + this.config.getPartner() + "&notify_id=" + notify_id;
        return checkUrl(veryfyUrl);
    }

    /**
     * 获取远程服务器ATN结果
     * Get the remote server ATN result
     *
     * @param urlvalue 指定URL路径地址specified URL value
     * @return 服务器ATN结果Sever ATN result
     * 验证结果集：
     * Verification result:
     * invalid命令参数不对 出现这个错误，请检测返回处理中partner和key是否为空
     * invalid Pls check whether the partner and key are null from notification
     * true 返回正确信息
     * true return the right info
     * false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
     * false pls check the firewall or the server block certain port,also pls note the expiration time is 1 minute
     */
    private static String checkUrl(String urlvalue) {
        String inputLine = "";

        try {
            URL url = new URL(urlvalue);
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection
                    .getInputStream()));
            inputLine = in.readLine().toString();
        } catch (Exception e) {
            e.printStackTrace();
            inputLine = "";
        }

        return inputLine;
    }

    /**
     * 除去数组中的空值和签名参数
     * Remove the blank ,sign and sign_type
     *
     * @param sArray 签名参数组A set of signature parameters
     * @return 去掉空值与签名参数后的新签名参数组The new signature paramaters with the blank ,sign and sign_type removed
     */
    public static Map<String, String> paraFilter(Map<String, String> sArray) {

        Map<String, String> result = new HashMap<String, String>();

        if (sArray == null || sArray.size() <= 0) {
            return result;
        }

        for (String key : sArray.keySet()) {
            String value = sArray.get(key);
            if (value == null || value.equals("") || key.equalsIgnoreCase("sign")
                    || key.equalsIgnoreCase("sign_type")) {
                continue;
            }
            result.put(key, value);
        }

        return result;
    }

    /**
     * 把数组所有元素排序，并按照“参数=参数值”的模式用“&”字符拼接成字符串
     * Rearrange parameters in the data set alphabetically and connect rearranged parameters with & like "parameter name=value"
     *
     * @param params 需要排序并参与字符拼接的参数组parameters need to be rearranged and connected
     * @return 拼接后字符串pre-sign string
     */
    public static String createLinkString(Map<String, String> params) {

        List<String> keys = new ArrayList<String>(params.keySet());
        Collections.sort(keys);

        String prestr = "";

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);

            if (i == keys.size() - 1) {//拼接时，不包括最后一个&字符do not include the last & when connecting
                prestr = prestr + key + "=" + value;
            } else {
                prestr = prestr + key + "=" + value + "&";
            }
        }

        return prestr;
    }
}
